<?php
/**
 * base class for files
 *
 * This file is part of Webappkit, a packages system for PHP web apps
 * @link http://webappkit.net
 * @package webappkit
 * @subpackage fs
 * @author J.Ducastel <nospam0@ducastel.name>
 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
 */
class WakFile implements iWakFS {

    /**
     * @param string $path
     */
    public function __construct($path) {
        $this->_path=$path;
    }

    /**
     * return given path
     * @return string
     */
    public function path() {
        return $this->_path;
    }

    /**
     * @return string
     */
    public function realPath() {
        return realpath($this->_path);
    }

    /**
     * get dirname
     * @return string
     */
    public function dirname() {
        return dirname($this->_path);
    }

    /**
     * @return string
     */
    public function basename() {
        return basename($this->_path);
    }

	/**
	* get file size in octets
	* @access public
	* @return int
	*/
	public function size() {
		return $this->exists()?filesize($this->realpath()):0;
	}/**/

	/**
	* does the file exists ?
	* @param
	* @access public
	* @return bool
	*/
	public function exists() {
		return file_exists($this->realpath());
	}/**/

	/**
	* returns md5 hash of the file's content
	* @param
	* @access public
	* @return
	*/
	public function md5() {
		return md5_file($this->realpath());
	}/**/

	/**
	* is the file readable ?
	* @access public
	* @return bool
	*/
	public function isReadable() {
		/*if (PHP_OS=='WINNT')
			return true;*/
		//clearstatcache();
		return is_readable($this->realpath());
	}/**/

	/**
	*
	* @param
	* @access public
	* @return
	*/
	public function isWritable() {
		/*if (PHP_OS=='WINNT')
			return true;*/
		return is_writable($this->realpath());
	}

    /**
	* read some bytes.
	* Reads 1Mo if bytes is not provided. From offset, if provided, else current offset.
	* @param int bytes optional 1mo by default
	* @param int offset optional uses current offset by default
	* @access public
	* @return mixed file content
	*/
	public function read($bytes=1048576, $offset=null) {
		/*if (!$this->isReadable()) {
			trigger_error('filenode::read : file'.$this->_path.' is not readable',E_USER_NOTICE);
			return false;
		}*/
        if (!$this->exists()) {
            // trigger_error('read() could not read if file dont exists, '.$this->_path);
            return false;
        }
		// getting handle
		if (!$handle=&$this->handle()) {
			trigger_error('filenode::read could not read '.$this->_path);
			return false;
		}
		// setting offset if necessary
        if (is_int($offset))
            $this->offset($offset);
        //echo "<p>offset is ".$this->offset().'</p>';
		return fread($handle,$bytes);
	}/**/

	/**
	* write some data.
	* From offset, if provided, else current offset.
	* @param mixed $content
	* @param int offset optional. uses current offset if omitted
	* @access public
	* @return bool
	*/
	public function write($content, $offset=null) {
		// is file writable
		/*if (!$this->isWritable()) {
			trigger_error('filenode::write : file '.$this->path.' is not writable',E_USER_NOTICE);
			return false;
		}*/
		// getting handle
		if (!$handle=&$this->handle()) {
			return false;
        }
		// setting offset if necessary
        if (is_int($offset))
            $this->offset($offset);
		return fwrite($handle,$content);
	}/**/

	/**
	* erase from offset
	* @param int offset
	* @return bool
	*/
	public function erase($offset=NULL) {
		// getting handle
		if (!$handle=&$this->handle())
			return false;
		// setting offset if necessary
		//$oldOffset=$this->offset();
		$from=$this->offset($offset);
		// echo $from;
		$ok=ftruncate($handle, $from);
		// echo $this->offset();
		// $this->offset($offset);
		return $ok;
	}

	/**
	* returns and edventually sets handle’s offset.
	* @param int offset optional sets new offset
	* @whence int SEEK_CUR|SEEK_END|SEEK_SET
	* @access public
	* @return int current offset
	*/
	public function offset($offset=null,$whence=NULL) {
		// getting file handle
		if (!$handle=&$this->handle()) {
            trigger_error("could not get handle to set new offset $offset");
			return false;
        }
		// is new offset provided
		if (is_int($offset)) {
			// setting whence
			switch ($whence) {
				case SEEK_CUR: // new offset is from current position
				case SEEK_END: // new offset is from the end
					break;
				default: // new offset is from start
					$whence=SEEK_SET;
			}
			// trying to set new offset
			fseek($handle,$offset,$whence);
		}
		return ftell($handle);
	}/**/

	/**
	* destroy file
	* @access public
	* @return bool
	*/
	public function delete() {
		if (!$this->exists()) return false;
		return unlink($this->realpath());
	}

    /**
     * creates file if don't exists
     * @return bool
     */
    public function create() {
        if (!$this->handle('w+'))
            return false;
        $this->close();
        return true;
    }

	/**
	* moves to new path
	* @param string $new_path
	* @access public
	* @return bool
	*/
	public function move($new_path) {
        if (!$this->exists())
            return false;
        $old=$this->realPath();
        $new=realpath($new_path).$this->basename();
        // echo $new; return false;
        if (!rename($old,$new))
            return false;
        $this->_path=$new;
        return true;
	}

    /**
     * renames file (new basename within same folder)
     * @return bool
     */
    public function rename($name) {
        $old=$this->realPath();
        $dir=$this->dirname();
        $new=$dir?($dir.'/'.$name):$name; // echo "<p>$old => $new</p>";
        if (!rename($old,$new))
            return false;
        $this->_path=$new;
        return true;
    }

	/**
	* sets offset to end of file
	* @return bool
	*/
	public function toEnd() {
		$off=$this->offset(0,SEEK_END); // echo "<p>offset = $off</p>";
        return true;
	}
	/**
	* sets offset to start of file
	* @return bool
	*/
	public function toStart() {
		return ($this->offset(0,SEEK_SET)==0);
	}

    /**
	* returns file handle
	* @access public
	* @param string $mode
	* @return resource file hande
	*/
	public function & handle($mode=null) {
		if (!$this->_handle) {
			// defining default mode if required
			if (is_null($mode)) {
				switch (true) {
					case $this->exists() and $this->isWritable():
                        // read/write, handle at start
						$mode='r+';
						break;
					case $this->exists():
                        // read only, handle at start
						$mode='r';
						break;
					default:
                        // read/write, create file, handle at start
						$mode='w+';
				}
			}
			// opening in requested mode
			if ($this->_handle=fopen($this->_path,$mode)) {
				$this->_mode=$mode;
			} else
				trigger_error('failed to open '.$this->_path.' in mode '.$mode);
		}
		return $this->_handle;
	}

    /**
     * close file handle
     * @return bool
     */
    public function close() {
        if (!fclose($this->handle()))
            return false;
        $this->_handle=null;
        return true;
    }

    /**
     * @var string file's path
     */
    protected $_path;

    /**
     * @var resource file's handle
     */
    protected $_handle;

    /**
     * @var string handle's mode
     */
	protected $_mode;

    /**
     * @var int handler position, in octets since start
     */
	// protected $_offset=0;
}
